﻿using System;
using System.Collections.Generic;

namespace Tuxedo.Compile
{
	internal abstract class IFPClassMember
	{
		public abstract MemberAttribute[] Attributes { get; }

		public static IFPClassMember ParseMember(string prefix, string classBasicName, TokenStream tokens)
		{
			List<MemberAttribute> attributes = new List<MemberAttribute>();
			while (tokens.IsNext("["))
			{
				attributes.Add(MemberAttribute.Parse(tokens));
			}

			Visibility visibility = Visibility.Private;

			if (tokens.PopIfPresent("public")) visibility = Visibility.Public;
			else if (tokens.PopIfPresent("private")) visibility = Visibility.Private;
			else if (tokens.PopIfPresent("protected")) visibility = Visibility.Protected;
			else if (tokens.PopIfPresent("internal")) visibility = Visibility.Internal;

			// TODO: pop these in any order until there are no more in this bunch.
			// Only one of them is allowed anyway.
			bool isVirtual = tokens.PopIfPresent("virtual");
			bool isOverride = tokens.PopIfPresent("override");
			bool isStatic = tokens.PopIfPresent("static");
			bool isReadonly = tokens.PopIfPresent("readonly");
			bool isAbstract = tokens.PopIfPresent("abstract");
			bool isConst = tokens.PopIfPresent("const");

			UnverifiedType outputType = UnverifiedType.Parse(tokens, false);

			if (outputType.Name == classBasicName && tokens.PopIfPresent("("))
			{
				if (isAbstract)
				{
					throw new Exception("Constructor cannot be abstract.");
				}

				// constructor
				List<string> parameterNames = new List<string>();
				List<bool> isParams = new List<bool>();
				List<UnverifiedType> parameterTypes = new List<UnverifiedType>();
				if (!tokens.PopIfPresent(")"))
				{
					isParams.Add(tokens.PopIfPresent("params"));
					parameterTypes.Add(UnverifiedType.Parse(tokens, false));
					parameterNames.Add(tokens.PopIdentifier());
					while (tokens.PopIfPresent(","))
					{
						isParams.Add(tokens.PopIfPresent("params"));
						parameterTypes.Add(UnverifiedType.Parse(tokens, false));
						parameterNames.Add(tokens.PopIdentifier());
					}

					tokens.PopExpected(")");
				}

				List<Expression> nextConstructorArgs = new List<Expression>();
				bool nextConstructorIsBase = false;
				bool hasNextConstructor = false;

				if (tokens.PopIfPresent(":"))
				{
					hasNextConstructor = true;
					if (!tokens.PopIfPresent("this"))
					{
						nextConstructorIsBase = true;
						tokens.PopExpected("base");
					}

					tokens.PopExpected("(");
					while (!tokens.PopIfPresent(")"))
					{
						if (nextConstructorArgs.Count > 0)
						{
							tokens.PopExpected(",");
						}
						nextConstructorArgs.Add(Expression.ParseExpression(tokens));
					}
				}

				IList<CodeLine> body = CodeLine.ParseBlock(tokens, false);

				return new ConstructorDefinition(
					visibility,
					isStatic,
					hasNextConstructor,
					nextConstructorArgs,
					nextConstructorIsBase,
					body,
					parameterNames,
					parameterTypes,
					attributes);
			}

			string name = tokens.PopIdentifier();

			// property
			if (tokens.PopIfPresent("{"))
			{
				PropertyBody getter = null;
				PropertyBody setter = null;

				while (!tokens.PopIfPresent("}"))
				{
					Visibility localVisibility = visibility;

					if (tokens.PopIfPresent("private")) localVisibility = Visibility.Private;
					else if (tokens.PopIfPresent("public")) localVisibility = Visibility.Public;
					else if (tokens.PopIfPresent("protected")) localVisibility = Visibility.Protected;
					else if (tokens.PopIfPresent("internal")) localVisibility = Visibility.Internal;

					if (tokens.PopIfPresent("get"))
					{
						if (getter != null)
						{
							throw new Exception("Property has multiple getters.");
						}
						if (tokens.PopIfPresent(";"))
						{
							getter = new PropertyBody(true, null, localVisibility);
						}
						else
						{
							IList<CodeLine> code = CodeLine.ParseBlock(tokens, false);
							getter = new PropertyBody(true, code, localVisibility);
						}
					}
					else if (tokens.PopIfPresent("set"))
					{
						if (setter != null)
						{
							throw new Exception("Property has multiple setters.");
						}
						if (tokens.PopIfPresent(";"))
						{
							setter = new PropertyBody(false, null, localVisibility);
						}
						else
						{
							IList<CodeLine> code = CodeLine.ParseBlock(tokens, false);
							setter = new PropertyBody(false, code, localVisibility);
						}
					}
					else
					{
						tokens.PopExpected("}");
					}
				}

				if (getter == null && setter == null)
				{
					throw new Exception("Empty property.");
				}

				return new Property(outputType, name, getter, setter, attributes);
			}

			// field
			if (tokens.PopIfPresent(";"))
			{
				if (isAbstract)
				{
					throw new Exception("Fields cannot be abstract.");
				}
				return new FieldDefinition(visibility, isStatic, name, outputType, null, isConst, attributes);
			}

			// field with default value
			if (tokens.PopIfPresent("="))
			{
				Expression defaultValue = Expression.ParseExpression(tokens);
				tokens.PopExpected(";");
				return new FieldDefinition(visibility, isStatic, name, outputType, defaultValue, isConst, attributes);
			}

			bool isMethod = tokens.PopIfPresent("<");
			UnverifiedType methodTypeParam = null;
			if (isMethod)
			{
				methodTypeParam = UnverifiedType.Parse(tokens, false);
				tokens.PopExpected(">");
				tokens.PopExpected("(");
			}

			// method
			if (tokens.PopIfPresent("(") || isMethod)
			{
				List<string> parameterNames = new List<string>();
				List<UnverifiedType> parameterTypes = new List<UnverifiedType>();
				List<string> specialParams = new List<string>();
				if (!tokens.PopIfPresent(")"))
				{
					specialParams.Add(tokens.PopAnyOf("params", "out", "ref"));
					parameterTypes.Add(UnverifiedType.Parse(tokens, false));
					parameterNames.Add(tokens.PopIdentifier());
					while (tokens.PopIfPresent(","))
					{
						specialParams.Add(tokens.PopAnyOf("params", "out", "ref"));
						parameterTypes.Add(UnverifiedType.Parse(tokens, false));
						parameterNames.Add(tokens.PopIdentifier());
					}
					tokens.PopExpected(")");
				};
				IList<CodeLine> body = null;

				if (!tokens.PopIfPresent(";"))
				{
					body = CodeLine.ParseBlock(tokens, false);
				}
				return new MethodDefinition(visibility, isStatic, name, outputType, parameterNames, parameterTypes, isOverride, body, isAbstract, specialParams, methodTypeParam, attributes);
			}

			throw new Exception("I don't know what this sort of member is.");
		}
	}
}
